package com.beta.backend.gateway;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.utils.ParamUtil;
import com.alibaba.nacos.common.utils.IPUtil;
import com.beta.backend.gateway.config.GatewayCfgBean;
import com.beta.backend.gateway.filter.ViewStatGatewayFilter;
import com.beta.cat.annotation.ExcludeInterceptor;
import com.beta.cat.application.BaseApplication;
import com.beta.cat.service.RedissonService;
import com.beta.cat.utils.MapUtils;
import com.beta.cat.utils.http.HttpManager;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 业务网关服务，提供3种过滤器，分别为BackendAuthGatewayFilter、DeveloperAuthGatewayFilter和SignetAuthGatewayFilter
 * BackendAuthGatewayFilter：授权认证过滤器，用于校验用户信息；已使用的本地化局点：1001、1002、1003
 * DeveloperAuthGatewayFilter：开发者授权（针对服务端、接口）；已使用的本地化局点：1003
 * SignetAuthGatewayFilter：资源拥有者过滤器，用于识别当前资源的分享者；已使用的本地化局点：1001
 * <p>
 * 可参照BackendAuthGatewayFilter自定义其他过滤器。
 */
@Slf4j
@Controller
@RequestMapping("/")
@SpringBootApplication
@EnableDiscoveryClient
public class BackendGatewayApplication extends BaseApplication {

    public static void main(String[] args) {
        SpringApplication.run(BackendGatewayApplication.class, args);
    }

    @Autowired(required = false)
    private DiscoveryClient client;
    @Autowired(required = false)
    private NacosConfigManager configManager;
    @Autowired(required = false)
    private List<DataSource> dataSources;
    @Autowired
    private GatewayCfgBean gatewayCfgBean;
    @Autowired
    private RedissonService redissonService;

    /**
     * 服务存活检查
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/heartbeat")
    @ResponseBody
    public String heartbeat() {
        return "UP";
    }

    /**
     * 全链路健康检查
     *
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/health")
    @ResponseBody
    public String health() {
        if (client != null) {
            try {//检测注册中心是否存活
                client.getServices();
            } catch (Exception e) {
                throw new RuntimeException("注册中心不可用！");
            }
        }
        if (CollectionUtils.isEmpty(dataSources)) {
            return "UP";
        }
        for (DataSource dataSource : dataSources) {
            try {
                try (Connection conn = dataSources.get(0).getConnection()) {
                    try (PreparedStatement statement = conn.prepareStatement(gatewayCfgBean.getValidationSQL())) {
                        try (ResultSet rs = statement.executeQuery()) {
                            rs.next();
                        }
                    }
                }
            } catch (Exception e) {
                log.error("数据库[{}]检查失败，不可用!", dataSource, e);
                throw new RuntimeException("数据库检查失败！");
            }
        }
        return "UP";
    }

    /**
     * 主界面，列举了所有的监控功能
     *
     * @throws IOException
     */
    @ExcludeInterceptor
    @RequestMapping("/dashboard")
    public Mono<Void> dashboard(ServerWebExchange exchange) throws IOException {
        if (!gatewayCfgBean.isConfigEnable()) {
            return renderHtml(exchange, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return renderHtml(exchange, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "没有可用应用");
        }
        StringBuffer html = new StringBuffer("监控功能汇总如下：\n");
        html.append("<ul>");
        html.append("<li><a href='services?appName=' title='appName：可以指定查看具体哪个服务部署的节点情况'>所有服务信息的节点情况</a></li>");
        html.append("<li><a href='service/info?appName=' title='appName：具体服务的名称，即：/services里面返回的serviceId'>查看具体服务的运行信息，如：内存情况、CPU情况及运行情况等</a></li>");
        html.append("<li><a href='service/env?appName=' title='appName：具体服务的名称，即：/services里面返回的serviceId'>查看指定系统的当前实际使用的配置</a></li>");
        html.append("<li><a href='service/verifyNetwork?fromAppName=&toAppName=&veriryPort=' title='fromAppName：发起方；" +
                "\ntoAppName：接收方；" +
                "\nveriryPort：不指定时，表示由系统自动收集注册中心上的端口进行验证；如果指定了，则验证指定的端口'>验证两个服务之间的网络是否畅通</a></li>");
        html.append("<li><a href='service/log?appName=&lines=100' title='appName：具体服务的名称，即：/services里面返回的serviceId；\nlines：指定查看最新的多少行，建议不要太多，防止内存不够用'>查看对应服务的最新日志</a></li>");
        html.append("<li><a href='nacos/config?appName=' title='appName：具体服务的名称，即：/services里面返回的serviceId'>查看服务在nacos上的配置</a></li>");
        html.append("<li><a href='nacos/nodes/status'>查看nacos各节点的状态</a></li>");
        html.append("<li><a href='view/stat'>功能的流量访问记录（不记录网关本身功能的访问量）</a></li>");
        html.append("</ul>");
        return renderHtml(exchange, HttpServletResponse.SC_OK, html.toString());
    }

    private Mono<Void> renderHtml(ServerWebExchange exchange, int code, String msg) throws IOException {
        ServerHttpResponse response = exchange.getResponse();
        response.setRawStatusCode(code);
        response.getHeaders().set("Content-Type", "text/html;charset=UTF-8");
        byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Flux.just(buffer));
    }

    /**
     * 查看注册中心的所有服务信息的节点情况
     *
     * @param appName 服务的名称
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/services")
    @ResponseBody
    public ResultVO services(String appName) {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return ResultVO.failure(500, "没有可用应用");
        }
        List<Map<String, Object>> ret = new ArrayList<>();
        if (StringUtils.isBlank(appName)) {
            for (String sn : services) {//组装：名称|可用节点|各节点ip
                Map<String, Object> one = getServiceInfo(sn);
                ret.add(one);
            }
        } else {
            for (String sn : services) {//组装：名称|可用节点|各节点ip
                if (!sn.toLowerCase().contains(appName.toLowerCase())) {
                    continue;
                }
                Map<String, Object> one = getServiceInfo(sn);
                ret.add(one);
            }
        }
        return ResultVO.success(ret);
    }

    /**
     * 查看具体服务的运行信息，如：内存情况、CPU情况及运行情况等
     *
     * @param appName 服务的名称，即：/services里面返回的名称
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/service/info")
    @ResponseBody
    public ResultVO serviceInfo(String appName) {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return ResultVO.failure(500, "没有可用应用");
        }
        Map<String, Map> ret = new LinkedHashMap<>();
        if (StringUtils.isBlank(appName)) {
            return ResultVO.failure(500, "请指定appName!");
        } else {
            Map<String, Object> one = getServiceInfo(appName);
            List<String> nodes = (List<String>) one.get("nodes");
            if (CollectionUtils.isEmpty(nodes)) {
                return ResultVO.failure(500, "对应的appName[" + appName + "]没有可用的节点信息！");
            }
            nodes.forEach(node -> {
                LinkedHashMap map = HttpManager.getInstance().get("http://" + node + "/monitor", LinkedHashMap.class);
                ret.put(node, map);
            });
        }
        return ResultVO.success(ret);
    }

    /**
     * 查看指定系统的当前实际使用的配置
     *
     * @param appName
     * @return
     */
    @ExcludeInterceptor
    @RequestMapping("/service/env")
    @ResponseBody
    public ResultVO serviceEnv(String appName) {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return ResultVO.failure(500, "没有可用应用");
        }
        Map<String, Map> ret = new LinkedHashMap<>();
        if (StringUtils.isBlank(appName)) {
            return ResultVO.failure(500, "请指定appName!");
        } else {
            Map<String, Object> one = getServiceInfo(appName);
            List<String> nodes = (List<String>) one.get("nodes");
            if (CollectionUtils.isEmpty(nodes)) {
                return ResultVO.failure(500, "对应的appName[" + appName + "]没有可用的节点信息！");
            }
            nodes.forEach(node -> {
                LinkedHashMap map = HttpManager.getInstance().get("http://" + node + "/env", LinkedHashMap.class);
                ret.put(node, map);
            });
        }
        return ResultVO.success(ret);
    }

    /**
     * 验证两个服务之间的网络是否畅通。
     *
     * @param fromAppName 发起方
     * @param toAppName   接收方
     * @param veriryPort  不指定时，表示由系统自动收集注册中心上的端口进行验证；如果指定了，则验证指定的端口
     */
    @ExcludeInterceptor
    @RequestMapping("/service/verifyNetwork")
    @ResponseBody
    public ResultVO verifyNetwork(String fromAppName, String toAppName, Integer veriryPort) {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return ResultVO.failure(500, "没有可用应用");
        }
        Map<String, Map> ret = new LinkedHashMap<>();
        if (StringUtils.isAnyBlank(fromAppName, toAppName)) {
            return ResultVO.failure(500, "请指定发送方和接收方的appName!");
        } else {
            Map<String, Object> from = getServiceInfo(fromAppName);
            List<String> fromNodes = (List<String>) from.get("nodes");
            if (CollectionUtils.isEmpty(fromNodes)) {
                return ResultVO.failure(500, "对应的fromAppName[" + fromAppName + "]没有可用的节点信息！");
            }
            Map<String, Object> to = getServiceInfo(toAppName);
            List<String> toNodes = (List<String>) to.get("nodes");
            if (CollectionUtils.isEmpty(toNodes)) {
                return ResultVO.failure(500, "对应的toAppName[" + toAppName + "]没有可用的节点信息！");
            }
            //每个发起节点，都要访问一下各个接收节点，确保所有的调用关系都验证到。
            fromNodes.forEach(fromNode -> {
                Map<String, Boolean> resultMap = new LinkedHashMap<>();
                try {
                    toNodes.forEach(toNode -> {
                        String host = toNode.split(":")[0].trim();
                        String port = veriryPort != null ? (veriryPort + "") : toNode.split(":")[1].trim();
                        Boolean good = HttpManager.getInstance().get("http://" + fromNode + "/verifyNetwork?host=" + host + "&port=" + port, Boolean.class);
                        resultMap.put(toNode, good);
                    });
                } catch (Exception e) {
                    resultMap.put("err:网关服务向发送方[" + fromNode + "]询问失败！" + e, false);
                }
                ret.put(fromNode, resultMap);
            });
        }
        return ResultVO.success(ret);
    }

    /**
     * 查看对应服务的最新日志
     *
     * @param appName 服务id
     * @param lines   最新的多少行
     * @return 各节点对应的日志内容
     */
    @ExcludeInterceptor
    @RequestMapping("/service/log")
    @ResponseBody
    public ResultVO serviceLog(String appName, Integer lines) {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        List<String> services = client.getServices();
        if (services == null || services.isEmpty()) {
            return ResultVO.failure(500, "没有可用应用");
        }
        Map<String, List<String>> ret = new LinkedHashMap<>();
        if (StringUtils.isBlank(appName)) {
            return ResultVO.failure(500, "请指定appName!");
        } else {
            Map<String, Object> one = getServiceInfo(appName);
            List<String> nodes = (List<String>) one.get("nodes");
            if (CollectionUtils.isEmpty(nodes)) {
                return ResultVO.failure(500, "对应的appName[" + appName + "]没有可用的节点信息！");
            }
            lines = lines == null ? 100 : lines;
            if (lines > 1000 || lines < 1) {
                return ResultVO.failure(500, "读取行数要求在 1到1000行之间");
            }
            Integer finalLines = lines;
            nodes.forEach(node -> {
                List<String> logs = HttpManager.getInstance().get("http://" + node + "/logs?lines=" + finalLines, ArrayList.class);
                ret.put(node, logs);
            });
        }
        return ResultVO.success(ret);
    }

    /**
     * 查看配置中心的配置
     *
     * @param appName
     * @return
     * @throws Exception
     */
    @ExcludeInterceptor
    @RequestMapping("/nacos/config")
    @ResponseBody
    public ResultVO nacosConfig(String appName) throws NacosException {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        Map<String, List<String>> ret = new LinkedHashMap<>();
        if (StringUtils.isBlank(appName)) {
            //获取所有配置文件名称+group
            ResultVO vo = services(null);
            List<Map<String, Object>> appNames = (List<Map<String, Object>>) vo.getData();
            for (Map<String, Object> an : appNames) {
                ret.put(MapUtils.getStringValue(an, "serviceId", ""), null);
            }
        } else {
            for (String group : "APPLICATION_GROUP,BUSINESS_GROUP,DEFAULT_GROUP,COMMON_GROUP".split(",")) {
                String dataIdPre = appName + "-" + group.substring(0, 3).toLowerCase() + ".";
                for (String suffix : "properties,yaml,yml".split(",")) {
                    String dataId = dataIdPre + suffix;
                    String content = configManager.getConfigService().getConfig(dataId, group, 10_000);
                    if (StringUtils.isBlank(content)) {
                        continue;
                    }
                    ret.put(dataId + "::" + group, Arrays.asList(content.split("\n")));
                }
            }
        }

        return ResultVO.success(ret);
    }

    /**
     * 查看nacos各节点的状态
     *
     * @return
     * @throws NacosException
     */
    @ExcludeInterceptor
    @RequestMapping("/nacos/nodes/status")
    @ResponseBody
    public ResultVO<List<JSONObject>> nacosNodeStatus() throws NacosException {
        if (!gatewayCfgBean.isConfigEnable()) {
            return ResultVO.failure(500, "该功能没有开放！请设置config.view.enable=true打开。");
        }
        String address = configManager.getNacosConfigProperties().getServerAddr();
        final String HTTPS = "https://";
        final String HTTP = "http://";
        List<String> serverAddrs = new ArrayList<String>();
        String[] serverAddrsArr = address.split(",");
        for (String serverAddr : serverAddrsArr) {
            if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) {
                serverAddrs.add(serverAddr);
            } else {
                String[] serverAddrArr = IPUtil.splitIPPortStr(serverAddr);
                if (serverAddrArr.length == 1) {
                    serverAddrs.add(HTTP + serverAddrArr[0] + IPUtil.IP_PORT_SPLITER + ParamUtil
                            .getDefaultServerPort());
                } else {
                    serverAddrs.add(HTTP + serverAddr);
                }
            }
        }
        List<JSONObject> nodes = new ArrayList<>();
        //一个个地址开始请求
        for (String addr : serverAddrs) {
            try {
                String content = HttpManager.getInstance().get(addr + "/nacos/v1/core/cluster/nodes", String.class);
                if (StringUtils.isBlank(content)) {
                    continue;
                }
                JSONObject obj = JSON.parseObject(content);
                if (obj.getIntValue("code") != 200) {
                    continue;
                }
                JSONArray list = obj.getJSONArray("data");
                for (int i = 0; i < list.size(); i++) {
                    JSONObject item = list.getJSONObject(i);
                    JSONObject one = new JSONObject();
                    one.put("state", item.getString("state"));
                    one.put("address", item.getString("address"));
                    nodes.add(one);
                }
                break;//请求第一个就行了
            } catch (Exception e) {
                log.error("请求nacos获取集群节点信息失败！", e);
            }
        }
        return nodes.isEmpty() ? ResultVO.failure(500, "集群全部宕机了！") : ResultVO.success(nodes);
    }

    /**
     * 功能的流量访问记录（不记录网关本身功能的访问量）
     *
     * @return
     * @throws NacosException
     */
    @ExcludeInterceptor
    @RequestMapping("/view/stat")
    @ResponseBody
    public ResultVO viewStat() {
        if (!gatewayCfgBean.isStatOpen()) {
            return ResultVO.failure(500, "该功能没有开放！请设置flow.stat.open=true打开。");
        }
        LocalDateTime now = LocalDateTime.now();
        Map<String, Map<String, Object>> ret = new LinkedHashMap<>();
        Map<String, Object> pv = Maps.newLinkedHashMap();
        Map<String, Object> uv = Maps.newLinkedHashMap();
        ret.put("pv", pv);
        ret.put("uv", uv);
        List<String> httpCodes = new ArrayList<>();
        try {
            RSet<Object> set = redissonService.getRedissonClient().getSet(ViewStatGatewayFilter.getHttpCodesKey());
            for (Object o : set) {
                String codeStr = o == null ? "-" : o.toString();
                httpCodes.add(codeStr);
            }
        } catch (Exception e) {
            log.warn("读取pv的状态码统计信息失败：{}", e.getMessage(), e);
        }
        //收集pv信息，时、日、月，以及各响应码对应的数量
        String hourTip = "当前小时内PV(" + now.format(ViewStatGatewayFilter.hourFomater) + ")";
        String dayTip = "今日PV(" + now.format(ViewStatGatewayFilter.dayFomater) + ")";
        String monthTip = "本月PV(" + now.format(ViewStatGatewayFilter.monthFomater) + ")";
        pv.put(hourTip, getLongValFromRedis(ViewStatGatewayFilter.getPvHourKey(null, 0)));
        pv.put(dayTip, getLongValFromRedis(ViewStatGatewayFilter.getPvDayKey(null, 0)));
        pv.put(monthTip, getLongValFromRedis(ViewStatGatewayFilter.getPvMonthKey(null, 0)));
        Map<String, Map<String, Object>> codeMap = Maps.newLinkedHashMap();//各个阶段的code统计
        codeMap.put(hourTip, Maps.newLinkedHashMap());
        codeMap.put(dayTip, Maps.newLinkedHashMap());
        codeMap.put(monthTip, Maps.newLinkedHashMap());
        pv.put("当前响应码统计", codeMap);
        for (String codeStr : httpCodes) {
            codeMap.get(hourTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvHourKey(codeStr, 0)));
            codeMap.get(dayTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvDayKey(codeStr, 0)));
            codeMap.get(monthTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvMonthKey(codeStr, 0)));
        }

        hourTip = "上个小时内PV(" + now.minusHours(1).format(ViewStatGatewayFilter.hourFomater) + ")";
        dayTip = "昨日PV(" + now.minusDays(1).format(ViewStatGatewayFilter.dayFomater) + ")";
        monthTip = "上月PV(" + now.minusMonths(1).format(ViewStatGatewayFilter.monthFomater) + ")";
        pv.put(hourTip, getLongValFromRedis(ViewStatGatewayFilter.getPvHourKey(null, 1)));
        pv.put(dayTip, getLongValFromRedis(ViewStatGatewayFilter.getPvDayKey(null, 1)));
        pv.put(monthTip, getLongValFromRedis(ViewStatGatewayFilter.getPvMonthKey(null, 1)));
        codeMap = Maps.newLinkedHashMap();//各个阶段的code统计
        codeMap.put(hourTip, Maps.newLinkedHashMap());
        codeMap.put(dayTip, Maps.newLinkedHashMap());
        codeMap.put(monthTip, Maps.newLinkedHashMap());
        pv.put("上期响应码统计", codeMap);
        for (String codeStr : httpCodes) {
            codeMap.get(hourTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvHourKey(codeStr, 1)));
            codeMap.get(dayTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvDayKey(codeStr, 1)));
            codeMap.get(monthTip).put(codeStr, getLongValFromRedis(ViewStatGatewayFilter.getPvMonthKey(codeStr, 1)));
        }

        //收集uv信息
        hourTip = "当前小时内UV(" + now.format(ViewStatGatewayFilter.hourFomater) + ")";
        dayTip = "今日DAU(" + now.format(ViewStatGatewayFilter.dayFomater) + ")";
        monthTip = "本月MAU(" + now.format(ViewStatGatewayFilter.monthFomater) + ")";
        uv.put(hourTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getUvHourKey(0)).count());
        uv.put(dayTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getDAUKey(0)).count());
        uv.put(monthTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getMAUKey(0)).count());

        hourTip = "上个小时内UV(" + now.minusHours(1).format(ViewStatGatewayFilter.hourFomater) + ")";
        dayTip = "昨日DAU(" + now.minusDays(1).format(ViewStatGatewayFilter.dayFomater) + ")";
        monthTip = "上月DAU(" + now.minusMonths(1).format(ViewStatGatewayFilter.monthFomater) + ")";
        uv.put(hourTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getUvHourKey(1)).count());
        uv.put(dayTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getDAUKey(1)).count());
        uv.put(monthTip, redissonService.getRedissonClient().getHyperLogLog(ViewStatGatewayFilter.getMAUKey(1)).count());
        return ResultVO.success(ret);
    }

    private long getLongValFromRedis(String key) {
        Long num = redissonService.get(key, Long.class);
        return num == null ? 0L : num;
    }

    private Map<String, Object> getServiceInfo(String serviceId) {
        Map<String, Object> one = new LinkedHashMap<>();
        one.put("serviceId", serviceId);
        List<ServiceInstance> is = client.getInstances(serviceId);
        List<String> node = new ArrayList<>();
        if (!CollectionUtils.isEmpty(is)) {
            for (ServiceInstance i : is) {
                node.add(i.getHost() + ":" + i.getPort());
            }
        }
        one.put("nodes", node);
        return one;
    }
}
